package com.gitee.wsl.platform.thread.task.bean

import com.gitee.wsl.platform.thread.task.runBlockingUninterruptible
import com.gitee.wsl.platform.thread.task.api.CloseableFun
import com.gitee.wsl.platform.thread.task.api.DelayedFun
import com.gitee.wsl.platform.thread.task.api.ProcessFun
import org.jetbrains.annotations.Blocking
import java.lang.AutoCloseable
import java.util.*
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executor
import java.util.function.Function
import kotlin.Exception
import kotlin.Throws


/**
 * A `Resource` represents a resource that can be acquired asynchronously.
 *
 *
 * This is similar to Java's [AutoCloseable], but it's more portable
 * and allows for asynchronous contexts.
 *
 *
 * `Resource` is the equivalent of [Task] for resource acquisition.
 *
 *
 * Sample usage:
 * <pre>`// -------------------------------------------------------
 * // SAMPLE METHODS
 * // -------------------------------------------------------
 *
 * // Creates temporary files — note that wrapping this in `Resource` is a
 * // win as `File` isn't `AutoCloseable`).
 * Resource<File> createTemporaryFile(String prefix, String suffix) {
 * return Resource.fromBlockingIO(() -> {
 * File tempFile = File.createTempFile(prefix, suffix);
 * tempFile.deleteOnExit(); // Ensure it gets deleted on exit
 * return Resource.Acquired.fromBlockingIO(
 * tempFile,
 * ignored -> tempFile.delete()
 * );
 * });
 * }
 *
 * // Creates `Reader` as a `Resource`
 * Resource<BufferedReader> openReader(File file) {
 * return Resource.fromAutoCloseable(() ->
 * new BufferedReader(
 * new InputStreamReader(
 * new FileInputStream(file),
 * StandardCharsets.UTF_8
 * )
 * ));
 * }
 *
 * // Creates `Writer` as a `Resource`
 * Resource<BufferedWriter> openWriter(File file) {
 * return Resource.fromAutoCloseable(() ->
 * new BufferedWriter(
 * new OutputStreamWriter(
 * new FileOutputStream(file),
 * StandardCharsets.UTF_8
 * )
 * ));
 * }
 *
 * // ...
 * // -------------------------------------------------------
 * // USAGE EXAMPLE (via try-with-resources)
 * // -------------------------------------------------------
 * try (
 * final var file = createTemporaryFile("test", ".txt").acquireBlocking()
 * ) {
 * try (final var writer = openWriter(file.get()).acquireBlocking()) {
 * writer.get().write("----\n");
 * writer.get().write("line 1\n");
 * writer.get().write("line 2\n");
 * writer.get().write("----\n");
 * }
 *
 * try (final var reader = openReader(file.get()).acquireBlocking()) {
 * final var builder = new StringBuilder();
 * String line;
 * while ((line = reader.get().readLine()) != null) {
 * builder.append(line).append("\n");
 * }
 * final String content = builder.toString();
 * assertEquals(
 * "----\nline 1\nline 2\n----\n",
 * content,
 * "File content should match the written lines"
 * );
 * }
 * }
`</pre> *
 */
class Resource<T> private constructor(
    private val  acquireTask: Task<Acquired<T>>
) {


    /**
     * Acquires the resource asynchronously via [Task], returning
     * it wrapped in [Acquired], which contains the logic for safe release.
     */
    fun acquireTask(): Task<Acquired<T>> {
        return acquireTask
    }

    /**
     * Acquires the resource via blocking I/O.
     *
     *
     * This method blocks the current thread until the resource is acquired.
     *
     *
     * @return an [Acquired] object that contains the acquired resource
     * and the logic to release it.
     * @throws ExecutionException if the resource acquisition failed with an exception
     * @throws InterruptedException if the current thread was interrupted while waiting
     * for the resource to be acquired
     */
//    @Blocking
//    @Throws(ExecutionException::class, InterruptedException::class)
//    fun acquireBlocking(): Acquired<T> {
//        return acquireBlocking(null)
//    }

    /**
     * Acquires the resource via blocking I/O using the specified executor.
     *
     *
     * This method blocks the current thread until the resource is acquired.
     *
     *
     * @param executor is the executor to use for executing the acquire task.
     * @return an [Acquired] object that contains the acquired resource
     * and the logic to release it.
     * @throws ExecutionException if the resource acquisition failed with an exception
     * @throws InterruptedException if the current thread was interrupted while waiting
     * for the resource to be acquired
     */
    @Blocking
    @Throws(ExecutionException::class, InterruptedException::class)
    fun acquireBlocking(executor: Executor? = null): Acquired<T> {
        return acquireTask().runBlocking(executor)
    }

    /**
     * Safely use the `Resource`, a method to use as an alternative to
     * Java's try-with-resources.
     *
     *
     * All the execution happens on the specified `executor`, which
     * is used for both acquiring the resource, executing the processing
     * function and releasing the resource.
     *
     *
     * Note that an asynchronous (Task-driven) alternative isn't provided,
     * as that would require an asynchronous evaluation model that's outside
     * the scope. E.g., work with Kotlin's coroutines, or Scala's Cats-Effect
     * to achieve that.
     *
     * @param executor is the executor to use for acquiring the resource and
     * for executing the processing function. If `null`, the
     * default executor for blocking I/O is used.
     * @param process is the processing function that can do I/O.
     *
     * @throws ExecutionException if either the resource acquisition or the
     * processing function fails with an exception.
     * @throws InterruptedException is thrown if the current thread was interrupted,
     * which can also interrupt the resource acquisition or the processing function.
     */
    @Blocking
    @Throws(InterruptedException::class, ExecutionException::class)
    fun <R> useBlocking(
        executor: Executor?=null,
        process: ProcessFun<in T, out R>,
    ): R? {
        val task = Task.fromBlockingIO({
            var finalizerCalled = false
            val acquired = acquireBlocking(executor)
            try {
                return@fromBlockingIO process.call(acquired.get)
            } catch (e: InterruptedException) {
                if (!finalizerCalled) {
                    finalizerCalled = true
                    acquired.releaseBlocking(ExitCase.canceled())
                }
                throw e
            } catch (e: Exception) {
                if (!finalizerCalled) {
                    finalizerCalled = true
                    acquired.releaseBlocking(ExitCase.failed(e))
                }
                throw e
            } finally {
                if (!finalizerCalled) {
                    acquired.releaseBlocking(ExitCase.succeeded())
                }
            }
        })

        return task.ensureRunningOnExecutor().runBlocking(executor)
    }

    /**
     * Safely use the `Resource`, a method to use as an alternative to
     * Java's try-with-resources.
     *
     *
     * This is an overload of [.useBlocking]
     * that uses the default executor for blocking I/O.
     */
//    @Blocking
//    @Throws(InterruptedException::class, ExecutionException::class)
//    fun <R> useBlocking(
//        process: ProcessFun<in T, out R>,
//    ): R? {
//        return useBlocking(null, process)
//    }

    /**
     * Tuple that represents an acquired resource and the logic to release it.
     *
     * @param get is the acquired resource
     * @param releaseTask is the (async) function that can release the resource
     */
    @JvmRecord
    data class Acquired<T>(
        val get: T,
        val releaseTask: Function<ExitCase, Task<Void>>,
    ) : AutoCloseable {
        /**
         * Used for asynchronous resource release.
         *
         *
         * @param exitCase signals the context in which the resource is being released.
         * @return a [Task] that releases the resource upon invocation.
         */
        fun releaseTask(exitCase: ExitCase): Task<Void> {
            return releaseTask.apply(exitCase)
        }

        /**
         * Releases the resource in a blocking manner, using the default executor.
         *
         *
         * This method can block the current thread until the resource is released.
         *
         *
         *
         * @param exitCase signals the context in which the resource is being released.
         */
        @Blocking
        @Throws(InterruptedException::class, ExecutionException::class)
        fun releaseBlocking(exitCase: ExitCase) {
            releaseBlocking(null, exitCase)
        }

        /**
         * Releases the resource in a blocking manner, using the specified executor.
         *
         *
         * This method can block the current thread until the resource is released.
         *
         *
         * @param executor is the executor to use for executing the release task.
         * @param exitCase signals the context in which the resource is being released.
         */
        @Blocking
        @Throws(InterruptedException::class, ExecutionException::class)
        fun releaseBlocking(
            executor: Executor?,
            exitCase: ExitCase,
        ) {
            executor.runBlockingUninterruptible( releaseTask(exitCase))
        }

        /**
         * Releases the resource in a blocking manner, using the default executor.
         *
         *
         * This being part of [AutoCloseable] means it can be used via a
         * try-with-resources block.
         */
        @Blocking
        @Throws(Exception::class)
        override fun close() {
            releaseBlocking(ExitCase.succeeded())
        }

        companion object {
            /**
             * Creates a "pure" `Acquired` instance with the given value —
             * i.e., it just wraps a value with the release function being a no-op.
             */
            fun <T> pure(value: T): Acquired<T> {
                return Acquired(value, NOOP)
            }

            /**
             * Creates an [Acquired] instance with a [CloseableFun]
             * release function that may do blocking I/O.
             *
             * @see Resource.fromBlockingIO
             */
            fun <T> fromBlockingIO(
                resource: T,
                release: CloseableFun,
            ): Acquired<T> {
                val task = release.toAsync() as Function<ExitCase, Task<Void>>
                val acquired = Acquired(resource, task)
                return acquired
            }

            private val NOOP: Function<ExitCase, Task<Void>> = Function { ignored: ExitCase -> Task.NOOP as Task<Void> }
        }
    }

    companion object {
        /**
         * Creates a [Resource] from an asynchronous task that acquires the resource.
         *
         *
         * The task should return an [Acquired] object that contains the
         * acquired resource and the logic to release it.
         */
        fun <T> fromAsync(
            acquire: Task<Acquired<T>>,
        ): Resource<T> {
            return Resource(acquire)
        }

        /**
         * Creates a [Resource] from a builder doing blocking I/O.
         *
         *
         * This method is useful for resources that are acquired via blocking I/O,
         * such as file handles, database connections, etc.
         */
        fun <T> fromBlockingIO(
            acquire: DelayedFun<out Acquired<T>>,
        ): Resource<T> {
            return fromAsync(Task.fromBlockingIO({
                val closeable= acquire.invoke()
                closeable
            }))
        }

        /**
         * Creates a [Resource] from a builder that returns an
         * [AutoCloseable] resource.
         */
        fun <T : AutoCloseable> fromAutoCloseable(
            acquire: DelayedFun<out T>,
        ): Resource<T> {
            return fromBlockingIO(DelayedFun {
                val resource = acquire.invoke()
                Acquired.fromBlockingIO<T>(resource, CloseableFun.fromAutoCloseable(resource))
            })
        }

        /**
         * Creates a "pure" `Resource`.
         *
         *
         * A "pure" resource is one that just wraps a known value, with no-op
         * release logic.
         *
         *
         * @see Task.pure
         */
        fun <T> pure(value: T): Resource<T> {
            return fromAsync(Task.pure(Acquired.pure(value)))
        }
    }
}
